{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b518b04cbfe0"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "906e07f6e562"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fb291b62b1aa"
      },
      "source": [
        "# Training and evaluation with the built-in methods"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b1820d9bdfb9"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/train_and_evaluate\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org에서 보기</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/train_and_evaluate.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">Google Colab에서 실행</a></td>\n",
        "  <td><a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ko/guide/keras/train_and_evaluate.ipynb\">     <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">    GitHub에서 소스 보기</a></td>\n",
        "  <td><a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/guide/keras/train_and_evaluate.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">노트북 다운로드</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8d4ac441b1fc"
      },
      "source": [
        "## 설정"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0472bf67b2bf"
      },
      "outputs": [],
      "source": [
        "import tensorflow as tf\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c16fe7fd6a6c"
      },
      "source": [
        "## 시작하기\n",
        "\n",
        "이 안내서는 훈련 및 유효성 검증을 위해 내장 API를 사용할 때의 훈련, 평가 및 예측 (추론) 모델 (예 : `model.fit()` , `model.evaluate()` , `model.predict()` )에 대해 설명합니다.\n",
        "\n",
        "고유한 훈련 단계 함수를 지정하면서 `fit()`을 사용하려면 <a href=\"https://www.tensorflow.org/guide/keras/customizing_what_happens_in_fit/\" data-md-type=\"link\">\" `fit()`에서 이루어지는 작업 사용자 정의하기\"</a> 가이드를 참조하세요.\n",
        "\n",
        "고유한 훈련 및 평가 루프를 처음부터 작성하려면 [\"처음부터 훈련 루프 작성\"](https://www.tensorflow.org/guide/keras/writing_a_training_loop_from_scratch/) 안내서를 참조하십시오.\n",
        "\n",
        "일반적으로, 내장 루프를 사용하든 직접 작성하든 관계없이 모델 훈련 및 유효성 검사는 모든 종류의 Keras 모델(순차 모델, Functional API로 작성된 모델 및 모델 하위 클래스화를 통해 처음부터 작성된 모델)에서 완전히 동일하게 작동합니다.\n",
        "\n",
        "이 가이드는 분산 교육에 대해서는 다루지 않습니다. 분산 교육에 대해서는 [멀티 GPU 및 분산 교육 안내서를](https://keras.io/guides/distributed_training/) 참조하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4e270faa413e"
      },
      "source": [
        "## API 개요 : 첫 번째 엔드 투 엔드 예제\n",
        "\n",
        "데이터를 모델의 내장 훈련 루프로 전달할 때는 **NumPy 배열**(데이터가 작고 메모리에 맞는 경우) 또는 **`tf.data Dataset` 객체**를 사용해야 합니다. 다음 몇 단락에서는 옵티마이저, 손실 및 메트릭을 사용하는 방법을 보여주기 위해 MNIST 데이터세트를 NumPy 배열로 사용하겠습니다.\n",
        "\n",
        "다음 모델을 고려해 보겠습니다 (여기서는 Functional API를 사용하여 빌드하지만 Sequential 모델 또는 하위 클래스 모델 일 수도 있음)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "170a6a18b2a3"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = layers.Dense(10, activation=\"softmax\", name=\"predictions\")(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e6d5724a90ab"
      },
      "source": [
        "일반적인 엔드 투 엔드 워크 플로는 다음과 같이 구성되어 있습니다.\n",
        "\n",
        "- 학습\n",
        "- 원래 교육 데이터에서 생성 된 홀드 아웃 세트에 대한 유효성 검사\n",
        "- 테스트 데이터에 대한 평가\n",
        "\n",
        "이 예에서는 MNIST 데이터를 사용합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8b55b3903edb"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "\n",
        "# Preprocess the data (these are NumPy arrays)\n",
        "x_train = x_train.reshape(60000, 784).astype(\"float32\") / 255\n",
        "x_test = x_test.reshape(10000, 784).astype(\"float32\") / 255\n",
        "\n",
        "y_train = y_train.astype(\"float32\")\n",
        "y_test = y_test.astype(\"float32\")\n",
        "\n",
        "# Reserve 10,000 samples for validation\n",
        "x_val = x_train[-10000:]\n",
        "y_val = y_train[-10000:]\n",
        "x_train = x_train[:-10000]\n",
        "y_train = y_train[:-10000]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "77a84eb1985b"
      },
      "source": [
        "훈련 구성(최적화 프로그램, 손실, 메트릭)을 지정합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "26a7f1819796"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(),  # Optimizer\n",
        "    # Loss function to minimize\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "    # List of metrics to monitor\n",
        "    metrics=[keras.metrics.SparseCategoricalAccuracy()],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ef28150b1eaa"
      },
      "source": [
        "`fit()`를 호출하여 데이터를 \"batch_size\" 크기의 \"배치\"로 분할하고 지정된 수의 \"epoch\"에 대해 전체 데이터세트를 반복 처리하여 모델을 훈련시킵니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0b92f67b105e"
      },
      "outputs": [],
      "source": [
        "print(\"Fit model on training data\")\n",
        "history = model.fit(\n",
        "    x_train,\n",
        "    y_train,\n",
        "    batch_size=64,\n",
        "    epochs=2,\n",
        "    # We pass some validation for\n",
        "    # monitoring validation loss and metrics\n",
        "    # at the end of each epoch\n",
        "    validation_data=(x_val, y_val),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0a1b698c6e39"
      },
      "source": [
        "반환되는 \"이력\" 객체는 훈련 중 손실 값과 메트릭 값에 대한 레코드를 유지합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "a20b8f5b9fcc"
      },
      "outputs": [],
      "source": [
        "history.history"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6105b646df66"
      },
      "source": [
        "`evaluate()`를 통해 테스트 데이터에 대해 모델을 평가합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "69f524a93f9d"
      },
      "outputs": [],
      "source": [
        "# Evaluate the model on the test data using `evaluate`\n",
        "print(\"Evaluate on test data\")\n",
        "results = model.evaluate(x_test, y_test, batch_size=128)\n",
        "print(\"test loss, test acc:\", results)\n",
        "\n",
        "# Generate predictions (probabilities -- the output of the last layer)\n",
        "# on new data using `predict`\n",
        "print(\"Generate predictions for 3 samples\")\n",
        "predictions = model.predict(x_test[:3])\n",
        "print(\"predictions shape:\", predictions.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f19d074eb88c"
      },
      "source": [
        "이제이 워크 플로의 각 부분을 자세히 검토하겠습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f3669f026d14"
      },
      "source": [
        "## `compile()` 메소드 : 손실, 메트릭 및 최적화 프로그램 지정\n",
        "\n",
        "`fit()` 으로 모델을 학습하려면 손실 함수, 최적화 프로그램 및 선택적으로 모니터링 할 일부 메트릭을 지정해야합니다.\n",
        "\n",
        "이것을 `compile()` 메소드의 인수로 모델에 전달합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "eb7a8deb494c"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "    metrics=[keras.metrics.SparseCategoricalAccuracy()],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c4061c977ac3"
      },
      "source": [
        "`metrics` 인수는 목록이어야합니다. 모델에는 여러 개의 메트릭이있을 수 있습니다.\n",
        "\n",
        "모델에 여러 개의 출력이있는 경우 각 출력에 대해 서로 다른 손실 및 메트릭을 지정하고 모델의 총 손실에 대한 각 출력의 기여도를 조정할 수 있습니다. 이에 대한 자세한 내용은 **\"다중 입력, 다중 출력 모델로 데이터 전달\"** 섹션에서 확인할 수 있습니다.\n",
        "\n",
        "기본 설정에 만족하면 대부분의 경우 최적화, 손실 및 메트릭을 문자열 식별자를 통해 바로 가기로 지정할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6444839ff300"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=\"rmsprop\",\n",
        "    loss=\"sparse_categorical_crossentropy\",\n",
        "    metrics=[\"sparse_categorical_accuracy\"],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5493ab963254"
      },
      "source": [
        "나중에 재사용하기 위해 모델 정의와 컴파일 단계를 함수에 넣겠습니다. 이 안내서의 여러 예에서 여러 번 호출합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "31c3e3c70f06"
      },
      "outputs": [],
      "source": [
        "def get_uncompiled_model():\n",
        "    inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "    x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "    x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "    outputs = layers.Dense(10, activation=\"softmax\", name=\"predictions\")(x)\n",
        "    model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "    return model\n",
        "\n",
        "\n",
        "def get_compiled_model():\n",
        "    model = get_uncompiled_model()\n",
        "    model.compile(\n",
        "        optimizer=\"rmsprop\",\n",
        "        loss=\"sparse_categorical_crossentropy\",\n",
        "        metrics=[\"sparse_categorical_accuracy\"],\n",
        "    )\n",
        "    return model\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "21b19c0a6a85"
      },
      "source": [
        "### 많은 내장 옵티 마이저, 손실 및 메트릭을 사용할 수 있습니다\n",
        "\n",
        "일반적으로 고유한 손실, 메트릭 또는 최적화 프로그램을 처음부터 새로 만들 필요가 없는데, Keras API에 필요한 것들이 이미 들어 있을 개연성이 높기 때문입니다.\n",
        "\n",
        "옵티마이저\n",
        "\n",
        "- `SGD()` (모멘텀이 있거나 없음)\n",
        "- `RMSprop()`\n",
        "- `Adam()`\n",
        "- 기타\n",
        "\n",
        "손실:\n",
        "\n",
        "- `MeanSquaredError()`\n",
        "- `KLDivergence()`\n",
        "- `CosineSimilarity()`\n",
        "- 기타\n",
        "\n",
        "메트릭\n",
        "\n",
        "- `AUC()`\n",
        "- `Precision()`\n",
        "- `Recall()`\n",
        "- 기타"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "d7abc0339980"
      },
      "source": [
        "### 관례 손실\n",
        "\n",
        "Keras로 커스텀 손실을 제공하는 두 가지 방법이 있습니다. 첫 번째 예는 입력 `y_true` 및 `y_pred` 를 받아들이는 함수를 만듭니다. 다음 예는 실제 데이터와 예측 간의 평균 제곱 오차를 계산하는 손실 함수를 보여줍니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cc4edd47bb5a"
      },
      "outputs": [],
      "source": [
        "def custom_mean_squared_error(y_true, y_pred):\n",
        "    return tf.math.reduce_mean(tf.square(y_true - y_pred))\n",
        "\n",
        "\n",
        "model = get_uncompiled_model()\n",
        "model.compile(optimizer=keras.optimizers.Adam(), loss=custom_mean_squared_error)\n",
        "\n",
        "# We need to one-hot encode the labels to use MSE\n",
        "y_train_one_hot = tf.one_hot(y_train, depth=10)\n",
        "model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "25b9fa7941ca"
      },
      "source": [
        "`y_true` 및 `y_pred` 이외의 매개 변수를 사용하는 손실 함수가 필요한 경우 `tf.keras.losses.Loss` 클래스를 서브 클래스 화하고 다음 두 메소드를 구현할 수 있습니다.\n",
        "\n",
        "- `__init__(self)` : 손실 함수 호출 중에 전달할 매개 변수를 승인합니다.\n",
        "- `call(self, y_true, y_pred)` : 목표 (y_true)와 모델 예측 (y_pred)을 사용하여 모델의 손실을 계산\n",
        "\n",
        "평균 제곱 오차를 사용하려고하지만 예측 값을 0.5에서 멀어지게하는 용어가 추가되었다고 가정 해 보겠습니다 (우리는 범주 형 목표가 원-핫 인코딩되고 0과 1 사이의 값을 취하는 것으로 가정). 이렇게하면 모델이 너무 자신감이없는 인센티브가 생겨 과적 합을 줄이는 데 도움이 될 수 있습니다 (시도 할 때까지 작동하는지 알 수 없음).\n",
        "\n",
        "방법은 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b09463a8c568"
      },
      "outputs": [],
      "source": [
        "class CustomMSE(keras.losses.Loss):\n",
        "    def __init__(self, regularization_factor=0.1, name=\"custom_mse\"):\n",
        "        super().__init__(name=name)\n",
        "        self.regularization_factor = regularization_factor\n",
        "\n",
        "    def call(self, y_true, y_pred):\n",
        "        mse = tf.math.reduce_mean(tf.square(y_true - y_pred))\n",
        "        reg = tf.math.reduce_mean(tf.square(0.5 - y_pred))\n",
        "        return mse + reg * self.regularization_factor\n",
        "\n",
        "\n",
        "model = get_uncompiled_model()\n",
        "model.compile(optimizer=keras.optimizers.Adam(), loss=CustomMSE())\n",
        "\n",
        "y_train_one_hot = tf.one_hot(y_train, depth=10)\n",
        "model.fit(x_train, y_train_one_hot, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b2141cc075a6"
      },
      "source": [
        "### 맞춤 측정 항목\n",
        "\n",
        "API의 일부가 아닌 메트릭이 필요한 경우 `tf.keras.metrics.Metric` 클래스를 서브 클래 싱하여 사용자 지정 메트릭을 쉽게 만들 수 있습니다. 4 가지 방법을 구현해야합니다.\n",
        "\n",
        "- `__init__(self)` . 여기서 메트릭에 대한 상태 변수를 만듭니다.\n",
        "- `update_state(self, y_true, y_pred, sample_weight=None)` 대상 y_true 및 모델 예측 y_pred를 사용하여 상태 변수를 업데이트합니다.\n",
        "- `result(self)` : 상태 변수를 사용하여 최종 결과를 계산합니다.\n",
        "- `reset_states(self)` : 메트릭의 상태를 다시 초기화합니다.\n",
        "\n",
        "경우에 따라 결과 계산이 매우 비싸고 주기적으로 만 수행되기 때문에 상태 업데이트와 결과 계산은 각각 `update_state()` 와 `result()` 에서 별도로 유지됩니다.\n",
        "\n",
        "다음은 `CategoricalTruePositives` 메트릭을 구현하는 방법을 보여주는 간단한 예제입니다.이 메트릭은 주어진 클래스에 속하는 것으로 올바르게 분류 된 샘플 수를 계산합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "05d6a6e7022d"
      },
      "outputs": [],
      "source": [
        "class CategoricalTruePositives(keras.metrics.Metric):\n",
        "    def __init__(self, name=\"categorical_true_positives\", **kwargs):\n",
        "        super(CategoricalTruePositives, self).__init__(name=name, **kwargs)\n",
        "        self.true_positives = self.add_weight(name=\"ctp\", initializer=\"zeros\")\n",
        "\n",
        "    def update_state(self, y_true, y_pred, sample_weight=None):\n",
        "        y_pred = tf.reshape(tf.argmax(y_pred, axis=1), shape=(-1, 1))\n",
        "        values = tf.cast(y_true, \"int32\") == tf.cast(y_pred, \"int32\")\n",
        "        values = tf.cast(values, \"float32\")\n",
        "        if sample_weight is not None:\n",
        "            sample_weight = tf.cast(sample_weight, \"float32\")\n",
        "            values = tf.multiply(values, sample_weight)\n",
        "        self.true_positives.assign_add(tf.reduce_sum(values))\n",
        "\n",
        "    def result(self):\n",
        "        return self.true_positives\n",
        "\n",
        "    def reset_states(self):\n",
        "        # The state of the metric will be reset at the start of each epoch.\n",
        "        self.true_positives.assign(0.0)\n",
        "\n",
        "\n",
        "model = get_uncompiled_model()\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(),\n",
        "    metrics=[CategoricalTruePositives()],\n",
        ")\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=3)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4bca8e959cda"
      },
      "source": [
        "### 표준 서명에 맞지 않는 손실 및 메트릭 처리하기\n",
        "\n",
        "거의 대부분의 손실과 메트릭은 `y_true` 및 `y_pred`에서 계산할 수 있습니다(여기서 `y_pred`가 모델의 출력). 그러나 모두가 그런 것은 아닙니다. 예를 들어, 정규화 손실은 레이어의 활성화만 요구할 수 있으며(이 경우 대상이 없음) 이 활성화는 모델 출력이 아닐 수 있습니다.\n",
        "\n",
        "이러한 경우 사용자 정의 레이어의 호출 메서드 내에서 `self.add_loss(loss_value)`를 호출할 수 있습니다. 이러한 방식으로 추가된 손실은 훈련 중 \"주요\" 손실(`compile()`로 전달되는 손실)에 추가됩니다. 다음은 활동 정규화를 추가하는 간단한 예입니다. 참고로 활동 정규화는 모든 Keras 레이어에 내장되어 있으며 이 레이어는 구체적인 예를 제공하기 위한 것입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b494d47437a0"
      },
      "outputs": [],
      "source": [
        "class ActivityRegularizationLayer(layers.Layer):\n",
        "    def call(self, inputs):\n",
        "        self.add_loss(tf.reduce_sum(inputs) * 0.1)\n",
        "        return inputs  # Pass-through layer.\n",
        "\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "\n",
        "# Insert activity regularization as a layer\n",
        "x = ActivityRegularizationLayer()(x)\n",
        "\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        ")\n",
        "\n",
        "# The displayed loss will be much higher than before\n",
        "# due to the regularization component.\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aaebb5829011"
      },
      "source": [
        "`add_metric()` 사용하여 메트릭 값 로깅에 대해 동일한 작업을 수행 할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "aa58091be092"
      },
      "outputs": [],
      "source": [
        "class MetricLoggingLayer(layers.Layer):\n",
        "    def call(self, inputs):\n",
        "        # The `aggregation` argument defines\n",
        "        # how to aggregate the per-batch values\n",
        "        # over each epoch:\n",
        "        # in this case we simply average them.\n",
        "        self.add_metric(\n",
        "            keras.backend.std(inputs), name=\"std_of_activation\", aggregation=\"mean\"\n",
        "        )\n",
        "        return inputs  # Pass-through layer.\n",
        "\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "\n",
        "# Insert std logging as a layer.\n",
        "x = MetricLoggingLayer()(x)\n",
        "\n",
        "x = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(learning_rate=1e-3),\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        ")\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f3c18154d057"
      },
      "source": [
        "[Functional API](https://www.tensorflow.org/guide/keras/functional/) 에서 `model.add_loss(loss_tensor)` 또는 `model.add_metric(metric_tensor, name, aggregation)` 호출 할 수도 있습니다.\n",
        "\n",
        "다음은 간단한 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0e19afe78b3a"
      },
      "outputs": [],
      "source": [
        "inputs = keras.Input(shape=(784,), name=\"digits\")\n",
        "x1 = layers.Dense(64, activation=\"relu\", name=\"dense_1\")(inputs)\n",
        "x2 = layers.Dense(64, activation=\"relu\", name=\"dense_2\")(x1)\n",
        "outputs = layers.Dense(10, name=\"predictions\")(x2)\n",
        "model = keras.Model(inputs=inputs, outputs=outputs)\n",
        "\n",
        "model.add_loss(tf.reduce_sum(x1) * 0.1)\n",
        "\n",
        "model.add_metric(keras.backend.std(x1), name=\"std_of_activation\", aggregation=\"mean\")\n",
        "\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
        ")\n",
        "model.fit(x_train, y_train, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b06d48035369"
      },
      "source": [
        "`add_loss()` 를 통해 손실을 전달하면 모델에는 이미 손실이 있으므로 손실 함수없이 `compile()` 을 호출 할 수 있습니다.\n",
        "\n",
        "다음 `LogisticEndpoint` 레이어를 생각해 보겠습니다. 이 레이어는 입력으로 targets 및 logits를 받아들이고 `add_loss()`를 통해 교차 엔트로피 손실을 추적합니다. 또한 `add_metric()`를 통해 분류 정확도도 추적합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "d56d2c504258"
      },
      "outputs": [],
      "source": [
        "class LogisticEndpoint(keras.layers.Layer):\n",
        "    def __init__(self, name=None):\n",
        "        super(LogisticEndpoint, self).__init__(name=name)\n",
        "        self.loss_fn = keras.losses.BinaryCrossentropy(from_logits=True)\n",
        "        self.accuracy_fn = keras.metrics.BinaryAccuracy()\n",
        "\n",
        "    def call(self, targets, logits, sample_weights=None):\n",
        "        # Compute the training-time loss value and add it\n",
        "        # to the layer using `self.add_loss()`.\n",
        "        loss = self.loss_fn(targets, logits, sample_weights)\n",
        "        self.add_loss(loss)\n",
        "\n",
        "        # Log accuracy as a metric and add it\n",
        "        # to the layer using `self.add_metric()`.\n",
        "        acc = self.accuracy_fn(targets, logits, sample_weights)\n",
        "        self.add_metric(acc, name=\"accuracy\")\n",
        "\n",
        "        # Return the inference-time prediction tensor (for `.predict()`).\n",
        "        return tf.nn.softmax(logits)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0698f3c98cbe"
      },
      "source": [
        "다음과 같이 `loss` 인수없이 컴파일 된 두 개의 입력 (입력 데이터 및 대상)이있는 모델에서 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0f6842f2bbe6"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "inputs = keras.Input(shape=(3,), name=\"inputs\")\n",
        "targets = keras.Input(shape=(10,), name=\"targets\")\n",
        "logits = keras.layers.Dense(10)(inputs)\n",
        "predictions = LogisticEndpoint(name=\"predictions\")(logits, targets)\n",
        "\n",
        "model = keras.Model(inputs=[inputs, targets], outputs=predictions)\n",
        "model.compile(optimizer=\"adam\")  # No loss argument!\n",
        "\n",
        "data = {\n",
        "    \"inputs\": np.random.random((3, 3)),\n",
        "    \"targets\": np.random.random((3, 10)),\n",
        "}\n",
        "model.fit(data)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "328b021aa6b8"
      },
      "source": [
        "다중 입력 모델 교육에 대한 자세한 내용은 **다중 입력, 다중 출력 모델로 데이터 전달** 섹션을 참조하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0536882b969c"
      },
      "source": [
        "### 유효성 검사 홀드아웃 세트를 자동으로 분리하기\n",
        "\n",
        "본 첫 번째 엔드 투 엔드 예제에서, 우리는 `validation_data` 인수를 사용하여 NumPy 배열의 튜플 `(x_val, y_val)` 을 모델에 전달하여 각 에포크의 끝에서 유효성 검증 손실 및 유효성 검증 메트릭을 평가합니다.\n",
        "\n",
        "또 다른 옵션: 인수 `validation_split`를 사용하여 유효성 검사 목적으로 훈련 데이터의 일부를 자동으로 예약할 수 있습니다. 인수 값은 유효성 검사를 위해 예약할 데이터 비율을 나타내므로 0보다 크고 1보다 작은 값으로 설정해야 합니다. 예를 들어, `validation_split=0.2`는 \"유효성 검사를 위해 데이터의 20%를 사용\"한다는 의미이고`validation_split=0.6`은 \"유효성 검사를 위해 데이터의 60%를 사용\"한다는 의미입니다.\n",
        "\n",
        "유효성을 계산하는 방법은 셔플 링 전에 맞춤 호출로 수신 한 배열의 마지막 x % 샘플을 가져 오는 것입니다.\n",
        "\n",
        "NumPy 데이터를 학습 할 때 `validation_split` 만 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "232fd59c751b"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "model.fit(x_train, y_train, batch_size=64, validation_split=0.2, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "42969af7ce01"
      },
      "source": [
        "## tf.data 데이터 세트의 교육 및 평가\n",
        "\n",
        "앞서 몇 단락에 걸쳐 손실, 메트릭 및 옵티마이저를 처리하는 방법을 살펴보았으며, 데이터가 NumPy 배열로 전달될 때 fit에서 `validation_data` 및 `validation_split` 인수를 사용하는 방법도 알아보았습니다.\n",
        "\n",
        "이제 데이터가 `tf.data.Dataset` 객체의 형태로 제공되는 경우를 살펴 보겠습니다.\n",
        "\n",
        "`tf.data` API는 빠르고 확장 가능한 방식으로 데이터를 로드하고 사전 처리하기 위한 TensorFlow 2.0의 유틸리티 세트입니다.\n",
        "\n",
        "`Datasets` 생성에 대한 자세한 설명은 [tf.data 설명서](https://www.tensorflow.org/guide/data)를 참조하세요.\n",
        "\n",
        "`Dataset` 인스턴스를 메서드 `fit()`, `evaluate()` 및 `predict()`로 직접 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3bf4ded224f8"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# First, let's create a training Dataset instance.\n",
        "# For the sake of our example, we'll use the same MNIST data as before.\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "# Shuffle and slice the dataset.\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Now we get a test dataset.\n",
        "test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))\n",
        "test_dataset = test_dataset.batch(64)\n",
        "\n",
        "# Since the dataset already takes care of batching,\n",
        "# we don't pass a `batch_size` argument.\n",
        "model.fit(train_dataset, epochs=3)\n",
        "\n",
        "# You can also evaluate or predict on a dataset.\n",
        "print(\"Evaluate\")\n",
        "result = model.evaluate(test_dataset)\n",
        "dict(zip(model.metrics_names, result))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "421d16914ce3"
      },
      "source": [
        "데이터세트는 각 epoch의 끝에서 재설정되므로 다음 epoch에서 재사용할 수 있습니다.\n",
        "\n",
        "이 데이터세트의 특정 배치 수에 대해서만 훈련을 실행하려면 다음 epoch로 이동하기 전에 이 데이터세트를 사용하여 모델이 실행해야 하는 훈련 단계의 수를 지정하는 `steps_per_epoch` 인수를 전달할 수 있습니다.\n",
        "\n",
        "이렇게 하면 각 epoch가 끝날 때 데이터세트가 재설정되지 않고 다음 배치를 계속 가져오게 됩니다. 무한 반복되는 데이터세트가 아니라면 결국 데이터세트의 데이터가 고갈됩니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "273c5dff16b4"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Prepare the training dataset\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Only use the 100 batches per epoch (that's 64 * 100 samples)\n",
        "model.fit(train_dataset, epochs=3, steps_per_epoch=100)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f2dcd180da7b"
      },
      "source": [
        "### 유효성 검사 데이터 집합 사용\n",
        "\n",
        "`fit()` 에서 `Dataset` 인스턴스를 `validation_data` 인수로 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bf4f3d78e69a"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Prepare the training dataset\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Prepare the validation dataset\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(64)\n",
        "\n",
        "model.fit(train_dataset, epochs=1, validation_data=val_dataset)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2e7f0ebf5f1d"
      },
      "source": [
        "각 시대가 끝날 때 모델은 유효성 검사 데이터 집합을 반복하고 유효성 검사 손실 및 유효성 검사 메트릭을 계산합니다.\n",
        "\n",
        "이 데이터세트의 특정 배치 수에 대해서만 유효성 검사를 실행하려면 유효성 검사를 중단하고 다음 epoch로 넘어가기 전에 유효성 검사 데이터세트에서 모델이 실행해야 하는 유효성 검사 단계의 수를 지정하는 `validation_steps` 인수를 전달할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f47342fed069"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "# Prepare the training dataset\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "# Prepare the validation dataset\n",
        "val_dataset = tf.data.Dataset.from_tensor_slices((x_val, y_val))\n",
        "val_dataset = val_dataset.batch(64)\n",
        "\n",
        "model.fit(\n",
        "    train_dataset,\n",
        "    epochs=1,\n",
        "    # Only run validation using the first 10 batches of the dataset\n",
        "    # using the `validation_steps` argument\n",
        "    validation_data=val_dataset,\n",
        "    validation_steps=10,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "67b4418e9f26"
      },
      "source": [
        "유효성 검사 데이터 세트는 사용 후마다 재설정되므로 항상 에포크에서 에포크까지 동일한 샘플을 평가하게됩니다.\n",
        "\n",
        "인수 `validation_split`(훈련 데이터로부터 홀드아웃 세트 생성)는 `Dataset` 객체로 훈련할 때는 지원되지 않는데, 이를 위해서는 데이터세트 샘플을 인덱싱할 수 있어야 하지만 `Dataset` API에서는 일반적으로 이것이 불가능하기 때문입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8160beb766a0"
      },
      "source": [
        "## 지원되는 다른 입력 형식\n",
        "\n",
        "NumPy 배열, 즉시 실행 텐서 및 TensorFlow `Datasets` 외에도 Pandas 데이터프레임을 사용하거나 데이터 및 레이블의 배치를 생성하는 Python 생성기에서 Keras 모델을 훈련할 수 있습니다.\n",
        "\n",
        "특히, `keras.utils.Sequence` 클래스는 멀티스레딩을 인식하고 셔플이 가능한 Python 데이터 생성기를 빌드하기 위한 간단한 인터페이스를 제공합니다.\n",
        "\n",
        "일반적으로 다음을 사용하는 것이 좋습니다.\n",
        "\n",
        "- 데이터가 작고 메모리에 맞는 경우 NumPy 입력 데이터\n",
        "- 큰 데이터세트가 있고 분산 훈련을 수행해야 하는 경우 `Dataset` 객체\n",
        "- 큰 데이터세트가 있고 TensorFlow에서 수행할 수 없는 많은 사용자 정의 Python 측 처리를 수행해야 하는 경우(예: 데이터 로드 또는 사전 처리를 위해 외부 라이브러리에 의존하는 경우) `Sequence` 객체\n",
        "\n",
        "## `keras.utils.Sequence` 객체를 입력으로 사용하기\n",
        "\n",
        "`keras.utils.Sequence`는 두 가지 중요한 속성을 가진 Python 생성기를 얻기 위해 하위 클래스화를 수행할 수 있는 유틸리티입니다.\n",
        "\n",
        "- 멀티 프로세싱과 잘 작동합니다.\n",
        "- 셔플할 수 있습니다(예: `fit()`에서 `shuffle=True`를 전달하는 경우).\n",
        "\n",
        "`Sequence` 는 두 가지 방법을 구현해야합니다.\n",
        "\n",
        "- `__getitem__`\n",
        "- `__len__`\n",
        "\n",
        "`__getitem__` 메소드는 완전한 배치를 리턴해야합니다. 신기원 사이의 데이터 세트를 수정하려면 `on_epoch_end` 구현할 수 있습니다.\n",
        "\n",
        "간단한 예를 들자면 다음과 같습니다.\n",
        "\n",
        "```python\n",
        "from skimage.io import imread\n",
        "from skimage.transform import resize\n",
        "import numpy as np\n",
        "\n",
        "# Here, `filenames` is list of path to the images\n",
        "# and `labels` are the associated labels.\n",
        "\n",
        "class CIFAR10Sequence(Sequence):\n",
        "    def __init__(self, filenames, labels, batch_size):\n",
        "        self.filenames, self.labels = filenames, labels\n",
        "        self.batch_size = batch_size\n",
        "\n",
        "    def __len__(self):\n",
        "        return int(np.ceil(len(self.filenames) / float(self.batch_size)))\n",
        "\n",
        "    def __getitem__(self, idx):\n",
        "        batch_x = self.filenames[idx * self.batch_size:(idx + 1) * self.batch_size]\n",
        "        batch_y = self.labels[idx * self.batch_size:(idx + 1) * self.batch_size]\n",
        "        return np.array([\n",
        "            resize(imread(filename), (200, 200))\n",
        "               for filename in batch_x]), np.array(batch_y)\n",
        "\n",
        "sequence = CIFAR10Sequence(filenames, labels, batch_size)\n",
        "model.fit(sequence, epochs=10)\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2a28343b1967"
      },
      "source": [
        "## 샘플 가중치 및 클래스 가중치 사용\n",
        "\n",
        "기본 설정을 사용하면 샘플의 무게가 데이터 세트의 빈도에 따라 결정됩니다. 샘플 빈도와 관계없이 데이터에 가중치를 부여하는 방법에는 두 가지가 있습니다.\n",
        "\n",
        "- 클래스 가중치\n",
        "- 샘플 무게"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f234a9a75b6d"
      },
      "source": [
        "### 클래스 가중치\n",
        "\n",
        "이 가중치는 `Model.fit()`에 대한 `class_weight` 인수로 사전을 전달하여 설정합니다. 이 사전은 클래스 인덱스를 이 클래스에 속한 샘플에 사용해야 하는 가중치에 매핑합니다.\n",
        "\n",
        "이 방법은 샘플링을 다시 수행하지 않고 클래스의 균형을 맞추거나 특정 클래스에 더 중요한 모델을 훈련시키는 데 사용할 수 있습니다.\n",
        "\n",
        "예를 들어, 데이터에서 클래스 \"0\"이 클래스 \"1\"로 표시된 것의 절반인 경우 `Model.fit(..., class_weight={0: 1., 1: 0.5})`을 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9929d26d91b8"
      },
      "source": [
        "다음은 클래스 #5(MNIST 데이터세트에서 숫자 \"5\")의 올바른 분류에 더 많은 중요성을 두도록 클래스 가중치 또는 샘플 가중치를 사용하는 NumPy 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f1844f2329a6"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "class_weight = {\n",
        "    0: 1.0,\n",
        "    1: 1.0,\n",
        "    2: 1.0,\n",
        "    3: 1.0,\n",
        "    4: 1.0,\n",
        "    # Set weight \"2\" for class \"5\",\n",
        "    # making this class 2x more important\n",
        "    5: 2.0,\n",
        "    6: 1.0,\n",
        "    7: 1.0,\n",
        "    8: 1.0,\n",
        "    9: 1.0,\n",
        "}\n",
        "\n",
        "print(\"Fit with class weight\")\n",
        "model = get_compiled_model()\n",
        "model.fit(x_train, y_train, class_weight=class_weight, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ce27221fad08"
      },
      "source": [
        "### 샘플 무게\n",
        "\n",
        "세밀한 제어를 위해 또는 분류기를 작성하지 않는 경우 \"샘플 가중치\"를 사용할 수 있습니다.\n",
        "\n",
        "- NumPy 데이터에서 학습하는 경우 : `sample_weight` 인수를 `Model.fit()` .\n",
        "- `tf.data` 또는 다른 종류의 반복자에서 훈련 할 때 : Yield `(input_batch, label_batch, sample_weight_batch)` 튜플.\n",
        "\n",
        "\"샘플 가중치\"배열은 배치에서 각 샘플이 총 손실을 계산하는 데 필요한 가중치를 지정하는 숫자 배열입니다. 불균형 분류 문제 (거의 보이지 않는 클래스에 더 많은 가중치를 부여하는 아이디어)에 일반적으로 사용됩니다.\n",
        "\n",
        "사용 된 가중치가 1과 0 인 경우, 어레이는 손실 함수에 대한 *마스크* 로 사용될 수 있습니다 (전체 손실에 대한 특정 샘플의 기여를 완전히 버림)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f9819d647793"
      },
      "outputs": [],
      "source": [
        "sample_weight = np.ones(shape=(len(y_train),))\n",
        "sample_weight[y_train == 5] = 2.0\n",
        "\n",
        "print(\"Fit with sample weight\")\n",
        "model = get_compiled_model()\n",
        "model.fit(x_train, y_train, sample_weight=sample_weight, batch_size=64, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eae5837c5f56"
      },
      "source": [
        "일치하는 `Dataset` 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "c870f3f0c66c"
      },
      "outputs": [],
      "source": [
        "sample_weight = np.ones(shape=(len(y_train),))\n",
        "sample_weight[y_train == 5] = 2.0\n",
        "\n",
        "# Create a Dataset that includes sample weights\n",
        "# (3rd element in the return tuple).\n",
        "train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train, sample_weight))\n",
        "\n",
        "# Shuffle and slice the dataset.\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "model = get_compiled_model()\n",
        "model.fit(train_dataset, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3963bfa348b0"
      },
      "source": [
        "## 다중 입력, 다중 출력 모델로 데이터 전달\n",
        "\n",
        "이전 예에서는 단일 입력(형상 `(764,)`의 텐서)과 단일 출력(형상 `(10,)`의 예측 텐서)이 있는 모델을 고려했습니다. 그렇다면 입력 또는 출력이 여러 개인 모델은 어떨까요?\n",
        "\n",
        "shape `(32, 32, 3)` ( `(height, width, channels)` 입력과 shape `(None, 10)` 의 시계열 입력 `(timesteps, features)` 하십시오. 우리의 모델은이 입력들의 조합으로부터 계산 된 두 개의 출력을 가질 것입니다 : \"점수\"(모양 `(1,)` )와 5 개의 클래스 (모양 `(5,)` )에 대한 확률 분포."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5f958449a057"
      },
      "outputs": [],
      "source": [
        "image_input = keras.Input(shape=(32, 32, 3), name=\"img_input\")\n",
        "timeseries_input = keras.Input(shape=(None, 10), name=\"ts_input\")\n",
        "\n",
        "x1 = layers.Conv2D(3, 3)(image_input)\n",
        "x1 = layers.GlobalMaxPooling2D()(x1)\n",
        "\n",
        "x2 = layers.Conv1D(3, 3)(timeseries_input)\n",
        "x2 = layers.GlobalMaxPooling1D()(x2)\n",
        "\n",
        "x = layers.concatenate([x1, x2])\n",
        "\n",
        "score_output = layers.Dense(1, name=\"score_output\")(x)\n",
        "class_output = layers.Dense(5, name=\"class_output\")(x)\n",
        "\n",
        "model = keras.Model(\n",
        "    inputs=[image_input, timeseries_input], outputs=[score_output, class_output]\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "df3ed34fe78b"
      },
      "source": [
        "이 모델을 플로팅하여 여기서 수행중인 작업을 명확하게 확인할 수 있습니다 (플롯에 표시된 셰이프는 샘플 별 셰이프가 아니라 배치 셰이프 임)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ac8c1baca9e3"
      },
      "outputs": [],
      "source": [
        "keras.utils.plot_model(model, \"multi_input_and_output_model.png\", show_shapes=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4d979e89b335"
      },
      "source": [
        "컴파일 타임에 손실 함수를 목록으로 전달하여 출력마다 다른 손실을 지정할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9655c0084d70"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f5fc73405283"
      },
      "source": [
        "모델에 단일 손실 함수만 전달하는 경우, 모든 출력에 동일한 손실 함수가 적용됩니다(여기서는 적합하지 않음).\n",
        "\n",
        "메트릭의 경우도 마찬가지입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b4c0c6c564bc"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],\n",
        "    metrics=[\n",
        "        [\n",
        "            keras.metrics.MeanAbsolutePercentageError(),\n",
        "            keras.metrics.MeanAbsoluteError(),\n",
        "        ],\n",
        "        [keras.metrics.CategoricalAccuracy()],\n",
        "    ],\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4dd9fb0343cc"
      },
      "source": [
        "출력 레이어에 이름을 지정 했으므로 dict를 통해 출력 당 손실 및 메트릭을 지정할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "42cb75110fc3"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={\n",
        "        \"score_output\": keras.losses.MeanSquaredError(),\n",
        "        \"class_output\": keras.losses.CategoricalCrossentropy(),\n",
        "    },\n",
        "    metrics={\n",
        "        \"score_output\": [\n",
        "            keras.metrics.MeanAbsolutePercentageError(),\n",
        "            keras.metrics.MeanAbsoluteError(),\n",
        "        ],\n",
        "        \"class_output\": [keras.metrics.CategoricalAccuracy()],\n",
        "    },\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bfd95ac0dd8b"
      },
      "source": [
        "출력이 두 개 이상인 경우 명시적 이름과 사전을 사용하는 것이 좋습니다.\n",
        "\n",
        "`loss_weights` 인수를 사용하여 출력별 손실에 서로 다른 가중치를 부여할 수 있습니다(예를 들어, 클래스 손실에 2x의 중요도를 부여하여 이 예에서 \"score\" 손실에 우선권을 줄 수 있음)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "23a71e5f5227"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={\n",
        "        \"score_output\": keras.losses.MeanSquaredError(),\n",
        "        \"class_output\": keras.losses.CategoricalCrossentropy(),\n",
        "    },\n",
        "    metrics={\n",
        "        \"score_output\": [\n",
        "            keras.metrics.MeanAbsolutePercentageError(),\n",
        "            keras.metrics.MeanAbsoluteError(),\n",
        "        ],\n",
        "        \"class_output\": [keras.metrics.CategoricalAccuracy()],\n",
        "    },\n",
        "    loss_weights={\"score_output\": 2.0, \"class_output\": 1.0},\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "367f598029e7"
      },
      "source": [
        "이러한 출력이 예측 용이지만 훈련 용이 아닌 경우 특정 출력에 대한 손실을 계산하지 않도록 선택할 수도 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "6d51aa372ef4"
      },
      "outputs": [],
      "source": [
        "# List loss version\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[None, keras.losses.CategoricalCrossentropy()],\n",
        ")\n",
        "\n",
        "# Or dict loss version\n",
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss={\"class_output\": keras.losses.CategoricalCrossentropy()},\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8314a8b3a7c7"
      },
      "source": [
        "적합하게 다중 입력 또는 다중 출력 모델에 데이터를 전달하는 것은 컴파일에서 손실 함수를 지정하는 것과 유사한 방식으로 작동합니다. **NumPy 배열 목록을** 전달할 수 있습니다 (손실 함수를 수신 한 출력에 1 : 1 매핑). **출력 이름을 NumPy 배열에 매핑합니다** ."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0539da84328b"
      },
      "outputs": [],
      "source": [
        "model.compile(\n",
        "    optimizer=keras.optimizers.RMSprop(1e-3),\n",
        "    loss=[keras.losses.MeanSquaredError(), keras.losses.CategoricalCrossentropy()],\n",
        ")\n",
        "\n",
        "# Generate dummy NumPy data\n",
        "img_data = np.random.random_sample(size=(100, 32, 32, 3))\n",
        "ts_data = np.random.random_sample(size=(100, 20, 10))\n",
        "score_targets = np.random.random_sample(size=(100, 1))\n",
        "class_targets = np.random.random_sample(size=(100, 5))\n",
        "\n",
        "# Fit on lists\n",
        "model.fit([img_data, ts_data], [score_targets, class_targets], batch_size=32, epochs=1)\n",
        "\n",
        "# Alternatively, fit on dicts\n",
        "model.fit(\n",
        "    {\"img_input\": img_data, \"ts_input\": ts_data},\n",
        "    {\"score_output\": score_targets, \"class_output\": class_targets},\n",
        "    batch_size=32,\n",
        "    epochs=1,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "e53eda8e1399"
      },
      "source": [
        "`Dataset` 사용 사례는 다음과 같습니다. NumPy 배열에서 수행 한 것과 유사하게 `Dataset` 은 튜플 튜플을 반환해야합니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4df41a12ed2c"
      },
      "outputs": [],
      "source": [
        "train_dataset = tf.data.Dataset.from_tensor_slices(\n",
        "    (\n",
        "        {\"img_input\": img_data, \"ts_input\": ts_data},\n",
        "        {\"score_output\": score_targets, \"class_output\": class_targets},\n",
        "    )\n",
        ")\n",
        "train_dataset = train_dataset.shuffle(buffer_size=1024).batch(64)\n",
        "\n",
        "model.fit(train_dataset, epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "38ebf30ce6ac"
      },
      "source": [
        "## 콜백 사용하기\n",
        "\n",
        "Keras의 콜백은 훈련 중 다른 시점(epoch의 시작, 배치의 끝, epoch의 끝 등)에서 호출되며 다음과 같은 동작을 구현하는 데 사용할 수 있는 객체입니다.\n",
        "\n",
        "- 훈련 중 서로 다른 시점에서 유효성 검사 수행(내장된 epoch당 유효성 검사에서 더욱 확장)\n",
        "- 정기적으로 또는 특정 정확도 임계값을 초과할 때 모델 검사점 설정\n",
        "- 훈련이 정체 된 것처럼 보일 때 모델의 학습 속도 변경\n",
        "- 훈련이 정체 된 것처럼 보일 때 최상위 레이어의 미세 조정\n",
        "- 교육이 종료되거나 특정 성능 임계 값을 초과 한 경우 전자 메일 또는 인스턴트 메시지 알림 보내기\n",
        "- 기타\n",
        "\n",
        "콜백은 `fit()` 에 대한 호출에 목록으로 전달 될 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "15036ddbee42"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "callbacks = [\n",
        "    keras.callbacks.EarlyStopping(\n",
        "        # Stop training when `val_loss` is no longer improving\n",
        "        monitor=\"val_loss\",\n",
        "        # \"no longer improving\" being defined as \"no better than 1e-2 less\"\n",
        "        min_delta=1e-2,\n",
        "        # \"no longer improving\" being further defined as \"for at least 2 epochs\"\n",
        "        patience=2,\n",
        "        verbose=1,\n",
        "    )\n",
        "]\n",
        "model.fit(\n",
        "    x_train,\n",
        "    y_train,\n",
        "    epochs=20,\n",
        "    batch_size=64,\n",
        "    callbacks=callbacks,\n",
        "    validation_split=0.2,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "303815509732"
      },
      "source": [
        "### 많은 내장 콜백을 사용할 수 있습니다\n",
        "\n",
        "- `ModelCheckpoint` : 주기적으로 모델을 저장합니다.\n",
        "- `EarlyStopping`: 훈련이 더 이상 유효성 검사 메트릭을 개선하지 못하는 경우 훈련을 중단합니다.\n",
        "- `TensorBoard` : 시각화 할 수 있습니다 정기적으로 쓰기 모델 로그 [TensorBoard](https://www.tensorflow.org/tensorboard) (섹션 \"시각화\"에서 자세한 내용).\n",
        "- `CSVLogger` : 손실 및 메트릭 데이터를 CSV 파일로 스트리밍합니다.\n",
        "- 기타\n",
        "\n",
        "전체 목록은 [콜백 설명서](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/) 를 참조하십시오.\n",
        "\n",
        "### 자신의 콜백 작성\n",
        "\n",
        "기본 클래스 `keras.callbacks.Callback` 을 확장하여 사용자 정의 콜백을 작성할 수 있습니다. 콜백은 클래스 속성 `self.model` 통해 연관된 모델에 액세스 할 수 있습니다.\n",
        "\n",
        "[사용자 정의 콜백을 작성하기 위한 전체 가이드](https://www.tensorflow.org/guide/keras/custom_callback/)를 꼭 읽어보세요. 다음은 훈련 중 배치별 손실 값 목록을 저장하는 간단한 예입니다.\n",
        "\n",
        "다음은 훈련 중 배치 별 손실 값 목록을 저장하는 간단한 예입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "b265d36ce608"
      },
      "outputs": [],
      "source": [
        "class LossHistory(keras.callbacks.Callback):\n",
        "    def on_train_begin(self, logs):\n",
        "        self.per_batch_losses = []\n",
        "\n",
        "    def on_batch_end(self, batch, logs):\n",
        "        self.per_batch_losses.append(logs.get(\"loss\"))\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5ee672524987"
      },
      "source": [
        "## 모델 검사점 설정하기\n",
        "\n",
        "상대적으로 큰 데이터세트에 대한 모델을 훈련시킬 때는 모델의 검사점을 빈번하게 저장하는 것이 중요합니다.\n",
        "\n",
        "이를 수행하는 가장 쉬운 방법은 `ModelCheckpoint` 콜백을 사용하는 것입니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "83614be57725"
      },
      "outputs": [],
      "source": [
        "model = get_compiled_model()\n",
        "\n",
        "callbacks = [\n",
        "    keras.callbacks.ModelCheckpoint(\n",
        "        # Path where to save the model\n",
        "        # The two parameters below mean that we will overwrite\n",
        "        # the current checkpoint if and only if\n",
        "        # the `val_loss` score has improved.\n",
        "        # The saved model name will include the current epoch.\n",
        "        filepath=\"mymodel_{epoch}\",\n",
        "        save_best_only=True,  # Only save a model if `val_loss` has improved.\n",
        "        monitor=\"val_loss\",\n",
        "        verbose=1,\n",
        "    )\n",
        "]\n",
        "model.fit(\n",
        "    x_train, y_train, epochs=2, batch_size=64, callbacks=callbacks, validation_split=0.2\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7f6afa36950c"
      },
      "source": [
        "`ModelCheckpoint` 콜백을 사용하여 내결함성을 구현할 수 있습니다. 훈련이 무작위로 중단 된 경우 모델의 마지막 저장된 상태에서 훈련을 다시 시작할 수있는 기능. 기본 예는 다음과 같습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "27ce92b2ad58"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "\n",
        "# Prepare a directory to store all the checkpoints.\n",
        "checkpoint_dir = \"./ckpt\"\n",
        "if not os.path.exists(checkpoint_dir):\n",
        "    os.makedirs(checkpoint_dir)\n",
        "\n",
        "\n",
        "def make_or_restore_model():\n",
        "    # Either restore the latest model, or create a fresh one\n",
        "    # if there is no checkpoint available.\n",
        "    checkpoints = [checkpoint_dir + \"/\" + name for name in os.listdir(checkpoint_dir)]\n",
        "    if checkpoints:\n",
        "        latest_checkpoint = max(checkpoints, key=os.path.getctime)\n",
        "        print(\"Restoring from\", latest_checkpoint)\n",
        "        return keras.models.load_model(latest_checkpoint)\n",
        "    print(\"Creating a new model\")\n",
        "    return get_compiled_model()\n",
        "\n",
        "\n",
        "model = make_or_restore_model()\n",
        "callbacks = [\n",
        "    # This callback saves a SavedModel every 100 batches.\n",
        "    # We include the training loss in the saved model name.\n",
        "    keras.callbacks.ModelCheckpoint(\n",
        "        filepath=checkpoint_dir + \"/ckpt-loss={loss:.2f}\", save_freq=100\n",
        "    )\n",
        "]\n",
        "model.fit(x_train, y_train, epochs=1, callbacks=callbacks)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "da3ab58d5235"
      },
      "source": [
        "또한 모델 저장 및 복원을 위해 자체 콜백을 작성하십시오.\n",
        "\n",
        "직렬화 및 저장에 대한 전체 안내서는 [모델 저장 및 직렬화 안내서를](https://www.tensorflow.org/guide/keras/save_and_serialize/) 참조하십시오."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b9342cc2ddba"
      },
      "source": [
        "## 학습 속도 일정 사용하기\n",
        "\n",
        "딥 러닝 모델을 훈련 할 때 일반적인 패턴은 훈련이 진행됨에 따라 점차적으로 학습을 줄이는 것입니다. 이것을 일반적으로 \"학습률 감소\"라고합니다.\n",
        "\n",
        "학습 붕괴 스케줄은 정적 인 (현재 에포크 또는 현재 배치 인덱스의 함수로서 미리 고정됨) 또는 동적 (모델의 현재 행동, 특히 검증 손실에 대응) 일 수있다.\n",
        "\n",
        "### 옵티마이저로 일정 전달하기\n",
        "\n",
        "옵티 마이저에서 schedule 객체를 `learning_rate` 인수로 전달하여 정적 학습 속도 감소 스케줄을 쉽게 사용할 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "684f0ab6d3de"
      },
      "outputs": [],
      "source": [
        "initial_learning_rate = 0.1\n",
        "lr_schedule = keras.optimizers.schedules.ExponentialDecay(\n",
        "    initial_learning_rate, decay_steps=100000, decay_rate=0.96, staircase=True\n",
        ")\n",
        "\n",
        "optimizer = keras.optimizers.RMSprop(learning_rate=lr_schedule)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7d742e44f535"
      },
      "source": [
        "`ExponentialDecay` , `PiecewiseConstantDecay` , `PolynomialDecay` 및 `InverseTimeDecay` 와 같은 몇 가지 기본 제공 일정을 사용할 수 있습니다.\n",
        "\n",
        "### 콜백을 사용하여 동적 학습 속도 일정 구현\n",
        "\n",
        "옵티마이저가 유효성 검사 메트릭에 액세스할 수 없으므로 이러한 일정 객체로는 동적 학습률 일정(예: 유효성 검사 손실이 더 이상 개선되지 않을 때 학습률 감소)을 달성할 수 없습니다.\n",
        "\n",
        "그러나 콜백은 유효성 검사 메트릭을 포함해 모든 메트릭에 액세스할 수 있습니다! 따라서 옵티마이저에서 현재 학습률을 수정하는 콜백을 사용하여 이 패턴을 달성할 수 있습니다. 실제로 이 부분이`ReduceLROnPlateau` 콜백으로 내장되어 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "b4a05f880175"
      },
      "source": [
        "## 훈련 중 손실 및 메트릭 시각화하기\n",
        "\n",
        "교육 중에 모델을 주시하는 가장 좋은 방법은 로컬에서 실행할 수있는 브라우저 기반 응용 프로그램 인 [TensorBoard](https://www.tensorflow.org/tensorboard) 를 사용하는 것입니다.\n",
        "\n",
        "- 교육 및 평가를위한 손실 및 지표의 라이브 플롯\n",
        "- (옵션) 레이어 활성화 히스토그램 시각화\n",
        "- (옵션) `Embedding` 레이어에서 학습한 포함된 공간의 3D 시각화\n",
        "\n",
        "pip와 함께 TensorFlow를 설치한 경우, 명령줄에서 TensorBoard를 시작할 수 있습니다.\n",
        "\n",
        "```\n",
        "tensorboard --logdir=/full_path_to_your_logs\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1fcf386a1dad"
      },
      "source": [
        "### TensorBoard 콜백 사용하기\n",
        "\n",
        "TensorBoard를 Keras 모델 및 fit 메서드와 함께 사용하는 가장 쉬운 방법은 `TensorBoard` 콜백입니다.\n",
        "\n",
        "가장 간단한 경우로, 콜백에서 로그를 작성할 위치만 지정하면 바로 쓸 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "f74247282ff6"
      },
      "outputs": [],
      "source": [
        "keras.callbacks.TensorBoard(\n",
        "    log_dir=\"/full_path_to_your_logs\",\n",
        "    histogram_freq=0,  # How often to log histogram visualizations\n",
        "    embeddings_freq=0,  # How often to log embedding visualizations\n",
        "    update_freq=\"epoch\",\n",
        ")  # How often to write logs (default: once per epoch)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "50cd5f8631fd"
      },
      "source": [
        "자세한 내용 [`TensorBoard` 콜백 설명서](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/tensorboard/)를 참조하세요."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "train_and_evaluate.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
